package se.perp.c4.control;

import java.util.ArrayList;
import java.util.List;
import se.perp.c4.control.iface.IMoveValidator;
import java.util.LinkedList;
import java.util.Queue;
import se.perp.c4.model.Actor;
import se.perp.c4.model.Actor.EActor;
import se.perp.c4.model.Territory;
import se.perp.c4.model.Territory.ETerritory;
import se.perp.c4.model.TerritoryCollection;
import static se.perp.c4.model.Actor.EActor.*;

class MoveValidator implements IMoveValidator {

    private TerritoryCollection terrColl;

    public MoveValidator(TerritoryCollection tc) {
        terrColl = tc;
    }

    public boolean isValidAttack(Actor attacker, Actor defender) {
        Territory to = terrColl.get(defender.where);
        return (attacker.side != defender.side) && isValidMove(attacker, to, defender);
    }

    public boolean isValidMove(Actor attacker, Territory to, Actor defender) {
        if (to == null) {
            return false;
        }

        Territory from = terrColl.get(attacker.where);

        // Special case: FRODO from DAGORLAD/GONDOR => MORDOR is always TRUE:
        if (attacker.who == FRODO && to.which == Territory.ETerritory.MORDOR &&
                from.hasLightNeighbor(Territory.ETerritory.MORDOR)) {
            return true;
        }

        boolean validForwardMove;
        boolean friendlyAndFree;
        boolean unfriendly;
        boolean attackingEnemy = defender != null && defender.side != attacker.side;

        if (attacker.isLight()) {
            validForwardMove = from.hasLightNeighbor(to.which);
            friendlyAndFree = !to.isOccupiedByDark() && !to.isFull();
            unfriendly = to.isOccupiedByDark();
        } else {
            validForwardMove = from.hasDarkNeighbor(to.which);
            friendlyAndFree = !to.isOccupiedByLight() && !to.isFull();
            unfriendly = to.isOccupiedByLight();
        }

        if (validForwardMove &&
                (friendlyAndFree || (unfriendly && attackingEnemy))) {
            return true;
        }

        if (defender == null) {
            return false;
        }

        switch (attacker.who) {
            case ARAGORN:
                return checkAragornMove(from, to);
            case WITCHKING:
                return checkWitchKingMove(from, to);
            case B_RIDERS:
                return checkRidersMove(from, to);
            case F_NAZGUL:
                return checkNazgulMove(to);
            default:
                return false;
        }
    }

    private boolean checkAragornMove(Territory from, Territory to) {
        return from.isAdjacentTo(to.which);
    }

    private boolean checkNazgulMove(Territory to) {
        return to.isOccupiedByLight() &&
                to.getOccupantCount() == 1;
    }

    private boolean checkRidersMove(Territory from, Territory to) {
        Queue<Territory> tqueue = new LinkedList<Territory>();

        for (int i = 0; i < from.darkNeighbors.length; i++) {
            tqueue.add(terrColl.get(from.darkNeighbors[i]));
        }

        while (!tqueue.isEmpty()) {

            Territory curr = tqueue.poll();

            boolean impassable =
                    curr.isOccupiedByLight() ||
                    curr.isFull();
            if (impassable) {
                continue;
            }

            // Breadth-first expansion
            for (int i = 0; i < curr.darkNeighbors.length; i++) {
                Territory candidate = terrColl.get(curr.darkNeighbors[i]);

                if (candidate == to) {
                    return true;
                }

                if (!tqueue.contains(candidate)) {
                    tqueue.add(candidate);
                }
            }
        }
        return false;

    }

    private boolean checkWitchKingMove(Territory from, Territory to) {
        return from.hasSidewaysNeighbor(to.which) &&
                to.isOccupiedByLight();
    }

    public List<ETerritory> getValidMoves(Actor actor) {
        ArrayList<ETerritory> list = new ArrayList<ETerritory>();

        for (Territory t : terrColl) {
            Actor enemy = null;
            if (t.getOccupantCount() > 0 && !t.isOccupiedBy(actor.side)) {
                enemy = t.getOccupants().get(0);
            }
            if (isValidMove(actor, t, enemy)) {
                list.add(t.which);
            }
        }

        return list;
    }
}
